Skip to content

Fix division by zero in clDice loss harmonic mean#8739

Open
Mr-Neutr0n wants to merge 2 commits intoProject-MONAI:devfrom
Mr-Neutr0n:fix/cldice-division-by-zero
Open

Fix division by zero in clDice loss harmonic mean#8739
Mr-Neutr0n wants to merge 2 commits intoProject-MONAI:devfrom
Mr-Neutr0n:fix/cldice-division-by-zero

Conversation

@Mr-Neutr0n
Copy link

Summary

  • Fix division by zero in SoftclDiceLoss and SoftDiceclDiceLoss when computing the harmonic mean of topology precision and sensitivity
  • Add a small epsilon (1e-7) to the denominator (tprec + tsens) to prevent NaN when both values are zero
  • Add test cases for zero-input and non-overlapping edge cases with smooth=0

Details

The clDice loss computes cl_dice = 1.0 - 2.0 * (tprec * tsens) / (tprec + tsens). When both tprec and tsens are zero (e.g., empty inputs, non-overlapping predictions/targets, or smooth=0), this results in 0/0 = NaN, which propagates through the loss and crashes training.

While the default smooth=1.0 prevents tprec and tsens from being exactly zero in most cases, setting smooth=0 (a valid configuration) exposes this bug whenever skeleton overlap is zero. The fix adds 1e-7 to the harmonic mean denominator, which:

  • Has negligible impact on normal computation (tprec, tsens are bounded in [0, 1])
  • Returns cl_dice = 1.0 (maximum loss) when both precision and sensitivity are zero, which is the correct semantic result
  • Is consistent with epsilon-based denominator guards used elsewhere in MONAI (e.g., smooth_dr in DiceLoss)

Test plan

  • Existing test_cldice_loss.py tests still pass (perfect overlap cases)
  • New test_zero_input_no_nan: verifies zero-valued inputs with smooth=0 do not produce NaN
  • New test_no_overlap_no_nan: verifies non-overlapping predictions/targets with smooth=0 do not produce NaN

…c mean

The clDice loss computes the harmonic mean of topology precision (tprec) and
sensitivity (tsens) as `2 * tprec * tsens / (tprec + tsens)`. When both values
are zero -- which occurs with empty inputs, non-overlapping predictions, or
when using smooth=0 -- this produces NaN due to 0/0 division.

Add a small epsilon (1e-7) to the harmonic mean denominator to prevent
division by zero. This has negligible effect on normal computation since
tprec and tsens are bounded in [0, 1], but prevents NaN loss values that
would otherwise crash training of tubular structure segmentation models.

Also add test cases covering zero-input and non-overlapping edge cases
with smooth=0 to verify the fix.

Signed-off-by: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 317fb23 and 480808d.

📒 Files selected for processing (1)
  • monai/losses/cldice.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • monai/losses/cldice.py

📝 Walkthrough

Walkthrough

The pull request adds a small epsilon (1e-7) to the denominator of the cl_dice computation in two forward methods to avoid division-by-zero/near-zero, preserving the original formula. It also adds two unit tests verifying that SoftclDiceLoss and SoftDiceclDiceLoss do not produce NaN for zero-valued inputs and for non-overlapping predictions when smooth=0.0.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding epsilon to prevent division by zero in clDice loss harmonic mean computation.
Description check ✅ Passed The description covers all required sections: summary, details, test plan, and types of changes. All key information about the fix and its rationale is provided.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ericspod
Copy link
Member

ericspod commented Mar 1, 2026

Hi @Mr-Neutr0n thanks for this change, there should be a value to prevent divide-by-zero but I would suggest following the pattern found with the other Dice losses. I would add a smooth_dr constructor argument as a new last argument with a value of 1e-7 and use that value in your forward methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants